using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Text;
using RideMe.AddIns.Interfaces;
using System.Diagnostics;
using System.Windows.Forms;
using System.Drawing;
using RideMe.AddIns.Console.Properties;

namespace RideMe.AddIns.Console
{
    /// <summary>
    /// This add-in provides access to the console for an application, allowing direct access to input commands and see output from the "script/console" for the app
    /// </summary>
    public class ConsoleAddIn : IAddIn
    {
        #region Private Variables

        private ConsoleProcess _consoleProcess;
        private TextBox _consoleTextBox;
        private ToolStripMenuItem _consoleMenuItem;
        private Collection<ConsoleEntry> _entries = new Collection<ConsoleEntry>();
        private int _commandIndex = int.MinValue;
        private RideMe.AddIns.Events.UserInterfaceEventSource.DockControlCreatedEventArgs _dockControlCreatedEventArgs = null;

        #endregion

        #region Methods

        /// <summary>
        /// This builds and returns the textbox used to display console output, and take in console input
        /// </summary>
        /// <param name="name"></param>
        private TextBox CreateTextBox(string name)
        {
            TextBox consoleTextBox = new TextBox();
            consoleTextBox.Name = name;
            consoleTextBox.Multiline = true;
            consoleTextBox.BackColor = Color.White;
            consoleTextBox.Visible = true;
            consoleTextBox.ScrollBars = ScrollBars.Vertical;
            consoleTextBox.KeyUp += new KeyEventHandler(_consoleTextBox_KeyUp);
            return consoleTextBox;
        }

        /// <summary>
        /// This builds and returns the console process used to communicate with script/console
        /// </summary>
        /// <returns></returns>
        private ConsoleProcess CreateConsoleProcess()
        {
            ConsoleProcess consoleProcess = new ConsoleProcess();
            consoleProcess.OutputReceived += new EventHandler<DataReceivedEventArgs>(_consoleProcess_OutputReceived);
            consoleProcess.ErrorReceived += new EventHandler<DataReceivedEventArgs>(_consoleProcess_ErrorReceived);
            consoleProcess.Completed += new EventHandler(_consoleProcess_Completed);
            return consoleProcess;
        }

        /// <summary>
        /// This writes the specified data to the process
        /// </summary>
        /// <param name="data"></param>
        private void WriteToProcess(string data)
        {
            if (_consoleProcess != null && _consoleProcess.TargetProcess != null)
                _consoleProcess.TargetProcess.StandardInput.WriteLine(data.Replace(Environment.NewLine, string.Empty));
        }

        #endregion

        #region Events

        /// <summary>
        /// Handles a key release event from the text box
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _consoleTextBox_KeyUp(object sender, KeyEventArgs e)
        {
            if (_consoleTextBox == null)
                return;
            //Enumerate the key pressed
            switch (e.KeyCode)
            {
                case Keys.Return:
                    //We are only looking for when we have entered a new line, which means there must be the line typed out, and the new line just started AT LEAST
                    if (_consoleTextBox.Lines.Length < 2)
                        return;
                    //Grab the last line
                    string lastLine = _consoleTextBox.Lines[_consoleTextBox.Lines.Length - 2];
                    //Ensure the console process is still there
                    if (_consoleProcess == null || _consoleProcess.TargetProcess == null || _consoleProcess.TargetProcess.HasExited)
                        return;
                    //Write out the command to the process
                    WriteToProcess(lastLine);
                    //Insert the command at the beginning of the command history
                    _entries.Add(new ConsoleEntry(lastLine, ConsoleEntry.ConsoleEntryType.COMMAND));
                    //Set the command index to int.MinValue so that we have reset our position in the command history
                    _commandIndex = int.MinValue;
                    //Remove the last line
                    _consoleTextBox.Text = _consoleTextBox.Text.Substring(0, _consoleTextBox.Text.Length - string.Format("{0}{1}", lastLine, Environment.NewLine).Length);
                    break;
                case Keys.Up:
                    //Scroll back through the command history
                    ScrollThroughHistory(+1);
                    break;
                case Keys.Down:
                    //Scroll forward through the command history
                    ScrollThroughHistory(-1);
                    break;
            }
        }

        /// <summary>
        /// This scrolls through the history, setting the current line to the specified scrolled entry within the history
        /// </summary>
        /// <param name="scroll"></param>
        private void ScrollThroughHistory(int scroll)
        {
            if (_consoleTextBox.InvokeRequired)
            {
                _consoleTextBox.Invoke(new MethodInvoker(
                    delegate
                    {
                        ScrollThroughHistory(scroll);
                    }));
            }
            else
            {
                if (_consoleTextBox == null)
                    return;
                //Lets start by ensuring we are at the end of the textbox
                _consoleTextBox.SelectionStart = _consoleTextBox.TextLength;
                _consoleTextBox.ScrollToCaret();
                //Grab the command list
                Collection<ConsoleEntry> commands = ConsoleEntry.Filter(ConsoleEntry.ConsoleEntryType.COMMAND, _entries);
                //Here we need to make sure we have two or more lines, and some previous commands for the up key to lookup
                if (_consoleTextBox.Lines.Length >= 2 && commands.Count > 0)
                {
                    //This will hold our re-built text string
                    string text = string.Empty;
                    //Loop through the lines
                    for (int i = 0; i < _consoleTextBox.Lines.Length; i++)
                    {
                        //If we are on the last line...
                        if (i == _consoleTextBox.Lines.Length - 1)
                        {
                            //...then we see if we already have a previous command still on the line, and if so, scroll through the command history as specified
                            if (_commandIndex != int.MinValue)
                            {
                                if (_commandIndex + scroll < 0)
                                    _commandIndex = commands.Count - 1;
                                else if (_commandIndex + scroll >= commands.Count)
                                    _commandIndex = 0;
                                else
                                    _commandIndex += scroll;
                            }
                            else
                                //Otherwise, we start at the beginning of the command history
                                _commandIndex = 0;
                            //Set the text of the line to be the selected command, remembering we need to work backwards
                            text += commands[(commands.Count - 1) - _commandIndex].Text;
                        }
                        else
                            //Otherwise, then output as normal
                            text += string.Format("{0}{1}", _consoleTextBox.Lines[i], Environment.NewLine);
                    }
                    //Set the new text
                    _consoleTextBox.Text = text;
                    //And again, scroll to the end
                    _consoleTextBox.SelectionStart = _consoleTextBox.TextLength;
                    _consoleTextBox.ScrollToCaret();
                }
            }
        }

        /// <summary>
        /// Handles error output from the console process
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleProcess_ErrorReceived(object sender, DataReceivedEventArgs e)
        {
            if (_consoleTextBox.InvokeRequired)
            {
                _consoleTextBox.Invoke(new MethodInvoker(
                    delegate
                    {
                        _consoleProcess_ErrorReceived(sender, e);
                    }));
            }
            else
            {
                _consoleTextBox.Text += string.Format("{0}{1}", e.Data, Environment.NewLine);
            }
        }

        /// <summary>
        /// Handles the console process completing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleProcess_Completed(object sender, EventArgs e)
        {
            while (!_consoleProcess.TargetProcess.HasExited) ;
            if (_consoleMenuItem.Owner.InvokeRequired)
            {
                _consoleMenuItem.Owner.Invoke(new MethodInvoker(
                    delegate
                    {
                        _consoleMenuItem.Tag = false;
                    }));
            }
            else
                _consoleMenuItem.Tag = false;
            if (_consoleTextBox.InvokeRequired)
            {
                _consoleTextBox.Invoke(new MethodInvoker(
                    delegate
                    {
                        if (_consoleTextBox != null && _consoleTextBox.Parent != null)
                            _dockControlCreatedEventArgs.RemoveControl(_consoleTextBox);
                        _consoleTextBox = null;
                    }));
            }
            else
            {
                if (_consoleTextBox != null && _consoleTextBox.Parent != null)
                    _dockControlCreatedEventArgs.RemoveControl(_consoleTextBox);
                _consoleTextBox = null;
            }
            _consoleProcess = null;
        }

        /// <summary>
        /// Handles output from the console process
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleProcess_OutputReceived(object sender, DataReceivedEventArgs e)
        {
            if (_consoleTextBox == null) return;

            if (_consoleTextBox.InvokeRequired)
            {
                _consoleTextBox.Invoke(new MethodInvoker(
                    delegate
                    {
                        _consoleProcess_OutputReceived(sender, e);
                    }));
            }
            else
            {
                _consoleTextBox.Text += string.Format("{0}{1}", e.Data, Environment.NewLine);
                _consoleTextBox.SelectionStart = _consoleTextBox.TextLength;
                _consoleTextBox.ScrollToCaret();
            }

        }

        /// <summary>
        /// Handles dock control creation to allow the add-in to setup its own controls
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserInterfaceEvents_BottomDockControlCreated(object sender, RideMe.AddIns.Events.UserInterfaceEventSource.DockControlCreatedEventArgs e)
        {
            _dockControlCreatedEventArgs = e;
        }

        /// <summary>
        /// This handles the dock control being closed off
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dockControl_Closing(object sender, EventArgs e)
        {
            _dockControlCreatedEventArgs.RemoveControl(_consoleTextBox);
            WriteToProcess("exit");
        }

        /// <summary>
        /// Handles menu creation to allow the add-in to setup its own menus
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserInterfaceEvents_MenuCreated(object sender, RideMe.AddIns.Events.UserInterfaceEventSource.MenuCreatedEventArgs e)
        {
            _consoleMenuItem = new ToolStripMenuItem("&Console");
            _consoleMenuItem.ShortcutKeys = Keys.Control | Keys.Shift | Keys.C;
            _consoleMenuItem.Tag = false;
            _consoleMenuItem.Enabled = false;
            _consoleMenuItem.Click += new EventHandler(_consoleMenuItem_Click);
            e.AddMenuItem("View", _consoleMenuItem);
        }

        /// <summary>
        /// Handles the clicking of the start/stop console menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleMenuItem_Click(object sender, EventArgs e)
        {
            if (_consoleTextBox != null && _consoleTextBox.InvokeRequired)
            {
                _consoleTextBox.Invoke(new MethodInvoker(
                    delegate
                    {
                        _consoleMenuItem_Click(sender, e);
                    }));
            }
            else
            {
                switch ((bool)_consoleMenuItem.Tag)
                {
                    case true:
                        WriteToProcess("exit");
                        break;
                    case false:
                        if(_consoleTextBox == null)
                            _consoleTextBox = CreateTextBox("Console");
                        _dockControlCreatedEventArgs.AddControl(_consoleTextBox, ((System.Drawing.Image)(Resources.ResourceManager.GetObject("console"))), false, null, new CancelEventHandler(dockControl_Closing));
                        if (_consoleProcess == null)
                            _consoleProcess = CreateConsoleProcess();
                        try
                        {
                            _consoleProcess.StartConsole();
                            _consoleMenuItem.Tag = true;
                            if (_dockControlCreatedEventArgs != null)
                                _dockControlCreatedEventArgs.ShowControl(_consoleTextBox);
                        }
                        catch (System.ComponentModel.Win32Exception)
                        {
                            MessageBox.Show(Resources.RubyNotFoundMessage, "Ruby not Found", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        break;

                }
            }
        }

        /// <summary>
        /// This handles the project closing, so that the console menu item can be disabled, and any running console can be closed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProjectEvents_ProjectClosed(object sender, RideMe.AddIns.Events.ProjectEventSource.FileEventArgs e)
        {
            _consoleMenuItem.Enabled = false;
            WriteToProcess("exit");
        }

        /// <summary>
        /// This handles the project opening, so that the console menu item can be enabled
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProjectEvents_ProjectOpened(object sender, RideMe.AddIns.Events.ProjectEventSource.FileEventArgs e)
        {
            _consoleMenuItem.Enabled = true;
        }

        #endregion

        #region IAddIn Members

        /// <summary>
        /// Returns the name of the add-in
        /// </summary>
        public string Name
        {
            get { return "Console Add-In"; }
        }

        /// <summary>
        /// Returns the author of the add-in
        /// </summary>
        public string Author
        {
            get { return "El Draper / Michael Leung"; }
        }

        /// <summary>
        /// Returns the description of the add-in
        /// </summary>
        public string Description
        {
            get { return "scipt/console window to interact with Active Record objects, etc."; }
        }

        /// <summary>
        /// This initializes the add-in and its resources
        /// </summary>
        /// <param name="context"></param>
        public void Init(IApplicationContext context)
        {
            _consoleProcess = new ConsoleProcess();
            _consoleProcess.OutputReceived += new EventHandler<DataReceivedEventArgs>(_consoleProcess_OutputReceived);
            _consoleProcess.Completed += new EventHandler(_consoleProcess_Completed);
            _consoleProcess.ErrorReceived += new EventHandler<DataReceivedEventArgs>(_consoleProcess_ErrorReceived);

            context.UserInterfaceEvents.MenuCreated += new EventHandler<RideMe.AddIns.Events.UserInterfaceEventSource.MenuCreatedEventArgs>(UserInterfaceEvents_MenuCreated);
            context.UserInterfaceEvents.BottomDockControlCreated += new EventHandler<RideMe.AddIns.Events.UserInterfaceEventSource.DockControlCreatedEventArgs>(UserInterfaceEvents_BottomDockControlCreated);

            context.ProjectEvents.ProjectOpened += new EventHandler<RideMe.AddIns.Events.ProjectEventSource.FileEventArgs>(ProjectEvents_ProjectOpened);
            context.ProjectEvents.ProjectClosed += new EventHandler<RideMe.AddIns.Events.ProjectEventSource.FileEventArgs>(ProjectEvents_ProjectClosed);
        }

        /// <summary>
        /// This tidies up any resources tied to the add-in
        /// </summary>
        public void Destroy()
        {
            if (!_consoleProcess.TargetProcess.HasExited)
                _consoleProcess.TargetProcess.Kill();
            _consoleTextBox = null;
            _consoleMenuItem = null;
        }

        #endregion
    }
}
